#include "Core.h"

// Sets result to 0xffff in the case that the input UTF8 sequence is bad.
// 32 bit 0xffffffff is an invalid UCS4 code point, so we can't use that as an error return value.
inline bool UTF8ToUCS4(const char *&p, const char *pEnd, uint32_t &result)
{
    // This could likely be implemented in a faster-executing way that uses tables.

    bool success = true;
    uint32_t c = 0xffff;
    const char *pNext = nullptr;

    if (p < pEnd)
    {
        uint8_t cChar0((uint8_t)*p), cChar1, cChar2, cChar3;

        // Asserts are disabled because we don't necessarily want to interrupt runtime execution due to this.
        // EASTL_ASSERT((cChar0 != 0xFE) && (cChar0 != 0xFF));     //  No byte can be 0xFE or 0xFF
        // Code below will effectively catch this error as it goes.

        if (cChar0 < 0x80)
        {
            pNext = p + 1;
            c = cChar0;
        }
        else
        {
            // EASTL_ASSERT((cChar0 & 0xC0) == 0xC0);              //  The top two bits need to be equal to 1
            if ((cChar0 & 0xC0) != 0xC0)
            {
                success = false;
                goto Failure;
            }

            if ((cChar0 & 0xE0) == 0xC0)
            {
                pNext = p + 2;

                if (pNext <= pEnd)
                {
                    c = (uint32_t)((cChar0 & 0x1F) << 6);
                    cChar1 = static_cast<uint8_t>(p[1]);
                    c |= cChar1 & 0x3F;

                    // EASTL_ASSERT((cChar1 & 0xC0) == 0x80);          //  All subsequent code should be b10xxxxxx
                    // EASTL_ASSERT(c >= 0x0080 && c < 0x0800);        //  Check that we have the smallest coding
                    if (!((cChar1 & 0xC0) == 0x80) ||
                        !(c >= 0x0080 && c < 0x0800))
                    {
                        success = false;
                        goto Failure;
                    }
                }
                else
                {
                    success = false;
                    goto Failure;
                }
            }
            else if ((cChar0 & 0xF0) == 0xE0)
            {
                pNext = p + 3;

                if (pNext <= pEnd)
                {
                    c = (uint32_t)((cChar0 & 0xF) << 12);
                    cChar1 = static_cast<uint8_t>(p[1]);
                    c |= (cChar1 & 0x3F) << 6;
                    cChar2 = static_cast<uint8_t>(p[2]);
                    c |= cChar2 & 0x3F;

                    // EASTL_ASSERT((cChar1 & 0xC0) == 0x80);            //  All subsequent code should be b10xxxxxx
                    // EASTL_ASSERT((cChar2 & 0xC0) == 0x80);            //  All subsequent code should be b10xxxxxx
                    // EASTL_ASSERT(c >= 0x00000800 && c <  0x00010000); //  Check that we have the smallest coding
                    if (!((cChar1 & 0xC0) == 0x80) ||
                        !((cChar2 & 0xC0) == 0x80) ||
                        !(c >= 0x00000800 && c < 0x00010000))
                    {
                        success = false;
                        goto Failure;
                    }
                }
                else
                {
                    success = false;
                    goto Failure;
                }
            }
            else if ((cChar0 & 0xF8) == 0xF0)
            {
                pNext = p + 4;

                if (pNext <= pEnd)
                {
                    c = (uint32_t)((cChar0 & 0x7) << 18);
                    cChar1 = static_cast<uint8_t>(p[1]);
                    c |= (uint32_t)((cChar1 & 0x3F) << 12);
                    cChar2 = static_cast<uint8_t>(p[2]);
                    c |= (cChar2 & 0x3F) << 6;
                    cChar3 = static_cast<uint8_t>(p[3]);
                    c |= cChar3 & 0x3F;

                    // EASTL_ASSERT((cChar0 & 0xf8) == 0xf0);            //  We handle the unicode but not UCS-4
                    // EASTL_ASSERT((cChar1 & 0xC0) == 0x80);            //  All subsequent code should be b10xxxxxx
                    // EASTL_ASSERT((cChar2 & 0xC0) == 0x80);            //  All subsequent code should be b10xxxxxx
                    // EASTL_ASSERT((cChar3 & 0xC0) == 0x80);            //  All subsequent code should be b10xxxxxx
                    // EASTL_ASSERT(c >= 0x00010000 && c <= 0x0010FFFF); //  Check that we have the smallest coding, Unicode and not ucs-4
                    if (!((cChar0 & 0xf8) == 0xf0) ||
                        !((cChar1 & 0xC0) == 0x80) ||
                        !((cChar2 & 0xC0) == 0x80) ||
                        !(c >= 0x00010000 && c <= 0x0010FFFF))
                    {
                        success = false;
                        goto Failure;
                    }
                }
                else
                {
                    success = false;
                    goto Failure;
                }
            }
            else if ((cChar0 & 0xFC) == 0xF8)
            {
                pNext = p + 4;

                if (pNext <= pEnd)
                {
                    // To do. We don't currently support extended UCS4 characters.
                }
                else
                {
                    success = false;
                    goto Failure;
                }
            }
            else if ((cChar0 & 0xFE) == 0xFC)
            {
                pNext = p + 5;

                if (pNext <= pEnd)
                {
                    // To do. We don't currently support extended UCS4 characters.
                }
                else
                {
                    success = false;
                    goto Failure;
                }
            }
            else
            {
                success = false;
                goto Failure;
            }
        }
    }
    else
        success = false;

Failure:
    if (success)
    {
        p = pNext;
        result = c;
    }
    else
    {
        p = p + 1;
        result = 0xffff;
    }

    return success;
}

// Sets result to 0xffff in the case that the input UTF8 sequence is bad.
// The effect of converting UTF8 codepoints > 0xffff to UCS2 (char16_t) is to set all
// such codepoints to 0xffff. EASTL doesn't have a concept of setting or maintaining
// error state for string conversions, though it does have a policy of converting
// impossible values to something without generating invalid strings or throwing exceptions.
inline bool UTF8ToUCS2(const char *&p, const char *pEnd, uint16_t &result)
{
    uint32_t u32;

    if (UTF8ToUCS4(p, pEnd, u32))
    {
        if (u32 <= 0xffff)
        {
            result = (uint16_t)u32;
            return true;
        }
    }

    result = 0xffff;
    return false;
}

bool DecodePart(const char *&pSrc, const char *pSrcEnd, char16_t *&pDest, char16_t *pDestEnd)
{
    bool success = true;

    while (success && (pSrc < pSrcEnd) && (pDest < pDestEnd))
        success = UTF8ToUCS2(pSrc, pSrcEnd, (uint16_t &)*pDest++);

    return success;
}